<template>
  <div id="bubbleApp_1"@click="onClick">
    <i id="bubbleradius"></i>
    <span class="bubblesSpan"v-for="(c, index) in circles" :style="style(c)" :key="c.key" :class="{ popped: c.popped }">&nbsp;</span>
  </div>

</template>

<script>
let globalId;

const rotate = (x, y, sin, cos, reverse) => {
  return reverse ? {
    x: cos*x + sin*y,
    y: cos*y - sin*x
  } : {
    x: cos*x - sin*y,
    y: cos*y + sin*x
  }
}

const flatten = arr => arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [])

export default {
 name:'bubbles',
  data () {
    return {
      circles: [],
      lastExec: null,
      hue: 250,
      lastCollisions: [],
      hiddenProperty: null,
      visibilityChangeEvent: null,
      moving: true
    }
  },
  computed: {},
  methods: {
    style (c) {
      return `top:${c.y}px;left:${c.x}px;box-shadow:0 0 2rem hsl(${c.hue}, 75%, 50%) inset`
    },
    update (tm) {
      if (!this.moving) {
        return
      }
      if(this.lastExec && this.circles.length) {
        let diff = (tm - this.lastExec)
        // let huediff = (this.hue + diff/30) % 360
        let box = this.$el.getBoundingClientRect()
        let radius = this.$el.querySelector('#bubbleradius').getBoundingClientRect().width

        let couples = []
        this.circles.filter(cc => !cc.popped).forEach(c1 => {
          this.circles.filter(cc => !cc.popped).forEach(c2 => {
            if (c1 !== c2) {
              couples.push([c1, c2])
            }
          })
        })

        let collisions = couples.filter(couple => {
          let dist = Math.sqrt(Math.pow(couple[0].y - couple[1].y, 2) +
              Math.pow(couple[0].x - couple[1].x, 2))
          return dist < radius * 2
        })

        let newcollisions = collisions.filter(couple => {
          let key = couple[0].key + couple[1].key
          return this.lastCollisions.indexOf(key) < 0
        })

        newcollisions.forEach(couple => {
          let a = couple[0]
          let b = couple[1]

          if (a.collisionFree && b.collisionFree) {
            if (false) {
              a.new_vx = (a.vx * (a.mass - b.mass) + (2 * b.mass * b.vx)) / (a.mass + b.mass)
              a.new_vy = (a.vy * (a.mass - b.mass) + (2 * b.mass * b.vy)) / (a.mass + b.mass)
            } else {
              let dx = b.x - a.x
              let dy = b.y - a.y
              let collisionAngle = Math.atan2(dy, dx)
              let sin = Math.sin(collisionAngle)
              let cos = Math.cos(collisionAngle)
              let pos_a = {x: 0, y: 0}
              let pos_b = rotate(dx, dy, sin, cos, true)
              let vel_a = rotate(a.vx, a.vy, sin, cos, true)
              let vel_b = rotate(b.vx, b.vy, sin, cos, true)
              let vxTotal = vel_a.x - vel_b.x
              vel_a.x = ((a.mass - b.mass) * vel_a.x + 2 * b.mass * vel_b.x) / (a.mass + b.mass)
              vel_b.x = vxTotal + vel_a.x
              pos_a.x += vel_a.x
              pos_b.x += vel_b.x

              let pos_a_final = rotate(pos_a.x, pos_a.y, sin, cos, false)
              let vel_a_final = rotate(vel_a.x, vel_a.y, sin, cos, false)
              a.new_x = a.x + pos_a_final.x
              a.new_y = a.y + pos_a_final.y;
              a.new_vx = vel_a_final.x;
              a.new_vy = vel_a_final.y;

              // each collision shows up in array twice: a->b and b->a
              // let pos_b_final = rotate(pos_b.x, pos_b.y, sin, cos, false)
              // let vel_b_final = rotate(vel_b.x, vel_b.y, sin, cos, false)
              // b.x = a.x + pos_b_final.x
              // b.y = a.y + pos_b_final.y
              // b.new_vx = vel_b_final.x;
              // b.new_vy = vel_b_final.y;
            }
          }
        })

        newcollisions.forEach(couple => {
          let a = couple[0]
          let b = couple[1]
          if (a.new_vy) {
            a.vx = a.new_vx
            a.vy = a.new_vy
            a.x = a.new_x
            a.y = a.new_y
            a.hue += 20
          }
        })

        this.lastCollisions = collisions.map(couple => {
          return couple[0].key + couple[1].key
        })

        let collided = [...new Set(flatten(newcollisions))]
        let collidedKeys = collided.map(c => c.key)


        this.circles.forEach(c => {
          c.collisionFree = c.collisionFree || (collidedKeys.indexOf(c.key) < 0)
          if (c.y < 0) {
            c.vy = Math.abs(c.vy)
          } else if (c.y > box.height) {
            c.vy = -Math.abs(c.vy)
          }
          if (c.x < 0) {
            c.vx = Math.abs(c.vx)
          } else if (c.x > box.width) {
            c.vx = -Math.abs(c.vx)
          }

          c.y += c.vy * diff
          c.x += c.vx * diff
        })
      }
      this.lastExec = tm
      globalId = requestAnimationFrame(this.update)
    },
    handleVisibilityChange () {
      if(!document[this.hiddenProperty]) {
        globalId = requestAnimationFrame(this.update)
        this.moving = true
      } else {
        cancelAnimationFrame(globalId)
        this.moving = false
      }
    },
    onClick (evt) {
      let m = {
        x: evt.pageX,
        y: evt.pageY-pageYOffset

      }
      let nearest = null
      let nearest_distsq = Infinity
      let dx, dy, distsq
      let radius = this.$el.querySelector('#bubbleradius').getBoundingClientRect().width
      this.circles.forEach(c => {
        dx = m.x - c.x
        dy = m.y - c.y
        distsq = dx*dx + dy*dy
        if (distsq < nearest_distsq && distsq < c.radius*c.radius) {
          nearest = c
          nearest_distsq = distsq
        }
      })
      if (nearest) {

        nearest.popped = true

        this.timer = setTimeout(()=>{   //设置延迟执行
          let idx = this.circles.indexOf(nearest)
          this.circles.splice(idx, 1);
          this.reAddOne();
        },100);


      }
    },

    reAddOne()
    {
      if (this.circles.length<3)
      {
        let box = this.$el.getBoundingClientRect()
        let radius = this.$el.querySelector('#bubbleradius').getBoundingClientRect().width
        let max = (box.width * box.height) / 300 / Math.pow(radius, 1.2)
        let index=Math.ceil(Math.random()*6);
        for (let i = 0; i <index ; i++) {
          this.circles.push({
            key: Math.random(),
            y: Math.random() * box.height*-1,
            x: Math.random() * box.width*-1,
            vx: (Math.random() / 5)*-1,
            vy: (Math.random() / 5)*-1,
            hue: Math.random() * 360,
            collisionFree: false,
            mass: 1,
            radius: radius,
            popped: false
          });
        }
      }else {
        return;
      }

    }
  },
  mounted () {
    let box = this.$el.getBoundingClientRect()
    let radius = this.$el.querySelector('#bubbleradius').getBoundingClientRect().width
    let max = (box.width * box.height) / 300 / Math.pow(radius, 1.2)
    for (let i = 0; i < max; i++) {

      this.circles.push({
        key: Math.random(),
        y: Math.random() * box.height,
        x: Math.random() * box.width,
        vx: Math.random() / 5,
        vy: Math.random() / 5,
        hue: Math.random() * 360,
        collisionFree: false,
        mass: 1,
        radius: radius,
        popped: false
      });
    }
    globalId = requestAnimationFrame(this.update)

    let hidden, visibilityChange;
    if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
      hidden = "hidden";
      visibilityChange = "visibilitychange";
    } else if (typeof document.msHidden !== "undefined") {
      hidden = "msHidden";
      visibilityChange = "msvisibilitychange";
    } else if (typeof document.webkitHidden !== "undefined") {
      hidden = "webkitHidden";
      visibilityChange = "webkitvisibilitychange";
    }
    this.hiddenProperty = hidden
    this.visibilityChangeEvent = visibilityChange
    document.addEventListener(this.visibilityChangeEvent, this.handleVisibilityChange, false)
  },
  beforeDestroy () {
    document.removeEventListener(this.visibilityChangeEvent, this.handleVisibilityChange, false)
  }

}
</script>

<style scoped>
@import "static/css/style.css";
</style>
